Lua Script Library
Lua Script Library Even a beginner programmer quickly learns the value of a library of common routines one can include in one's recipes. There are multiple ways of doing the same thing so if you have a way you think better just include it as an alternative. There's no point in arguing style. On the other hand, it's better to not to have multiple definitions for the very same named object. I, Gary Forbis, would like to propose the following guideline: Always define your functions and supporting global fields with at least two nodes. The first node is a table name idenifying a name space and the second either refines the name space or names the function. For example: math={} -- Return the absolute value of x function math.abs(x) if x < 0 then return -x else return x end end﻿ Also, include sample calls one can use to demonstrate the function: print(math.abs(-5)) -- will print 5 print(math.abs(5)) -- will also print 5 ﻿ Include attribution and copyright notice comments given in the code when you copy the code. Finally, reserve a namespace early. We can agree upon common library names. Where they already exist, such as math, please implement the semantics already identified for the members. Extensions to the standard libraries should be clearly identified as such. Well, that'smy introduction. Have fun and good scripting. 'Reserved Name Spaces' Reserve your name space here. If you want to include one of the routines you need to define the table first. Just include the line from the following for the specific function. base={} -- the lua standard library luaopen_base debug={} -- the lua standard library luaopen_debug fsl={} -- Foldit special library game={} -- game specific information for current run of script games={} -- game specific information saved in the script gary={} -- Gary Forbis routines or modifications of others' routines math={} -- the lua standard library luaopen_math io={} -- the lua standard library luaopen_io package={} -- the lua standard library luaopen_package string={} -- the lua standard library luaopen_string table={} -- the lua standard library luaopen_table 'base -- the lua standard library luaopen_base' Except where identified these routines are in the standard lua library luaopen_base. Please be as faithful as possible to to their defintions. The implementation here differs from lua in that they are buried down one layer from global. In standard lua these functions would not be prefaced by base. 'base.assert -- test for false or nil' Issues an error when the value of its argument v is false (i.e., nil or false); otherwise, returns all its arguments. message is an error message; when absent, it defaults to "assertion failed!" function base.assert(v, message) if (v nil) or (v false) then if message nil then print ("assertion failed!") else print(message) end assertion_failue=nil -- cause the script to fail after printing message assertion_failure() else return v end end I don't have access to any normal implementation of lua so am relyiing upon a best guess here. If lua returns on error then please correct. Use this routine when you want simple error checking without a bunch of fuss. -- example call to test band length in range base.assert((length >= 0 and length <= 20), "Band length out of range") 'fsl -- Foldit Special Library' You can find (or add) functions here that are of a general nature. ''fsl.Aminos -- A table of useful information about the residues. This table and the for loop support other functions such as fsl.IsHydrophobe and fsl.IsHydrophile. -- commented out residues not in Foldit (as of Nov 15, 2010) fsl.aminosLetterIndex=1 fsl.aminosShortIndex=2 fsl.aminosLongIndex=3 fsl.aminosPolarityIndex=4 fsl.aminosAcidityIndex=5 fsl.aminosHydropathyIndex=6 fsl.aminos = { {'a','Ala','Alanine', 'nonpolar','neutral', 1.8}, -- {'b','Asx','Asparagine or Aspartic acid' }, {'c','Cys','Cysteine', 'nonpolar','neutral', 2.5}, {'d','Asp','Aspartic acid', 'polar','negative', -3.5}, {'e','Glu','Glutamic acid', 'polar','negative', -3.5}, {'f','Phe','Phenylalanine','nonpolar','neutral', 2.8}, {'g','Gly','Glycine', 'nonpolar','neutral', -0.4}, {'h','His','Histidine', 'polar','neutral', -3.2}, {'i','Ile','Isoleucine', 'nonpolar','neutral', 4.5}, -- {'j','Xle','Leucine or Isoleucine' }, {'k','Lys','Lysine', 'polar','positive', -3.9}, {'l','Leu','Leucine', 'nonpolar','neutral', 3.8}, {'m','Met','Methionine ', 'nonpolar','neutral', 1.9}, {'n','Asn','Asparagine', 'polar','neutral', -3.5}, -- {'o','Pyl','Pyrrolysine' }, {'p','Pro','Proline', 'nonpolar','neutral', -1.6}, {'q','Gln','Glutamine', 'polar','neutral', -3.5}, {'r','Arg','Arginine', 'polar','positive', -4.5}, {'s','Ser','Serine', 'polar','neutral', -0.8}, {'t','Thr','Threonine', 'polar','neutral', -0.7}, -- {'u','Sec','Selenocysteine' }, {'v','Val','Valine', 'nonpolar','neutral', 4.2}, {'w','Trp','Tryptophan', 'nonpolar','neutral', -0.9}, -- {'x','Xaa','Unspecified or unknown amino acid' }, {'y','Tyr','Tyrosine', 'polar','neutral', -1.3}, -- {'z','Glx','Glutamine or glutamic acid' } } -- turn letter into alternative direct index for i=1,#fsl.aminos do fsl.aminos[fsl.aminosifsl.aminosLetterIndex] = fsl.aminosi end ''See fsl.IsHydrophobe for an example ''fsl.Default -- Return value or default'' Edit Rather than coding the same if over and over this function lets you use a single line function fsl.Default(value,default) if value nil then return default else return value end end Very useful function function myfunction(optionalparam) print(fsl.Default(optionalparam,"")) end myfunction() -- prints myfunction("passed") -- prints passed ''fsl.FindMutableSegments -- get a list of mutable segments Many foldit puzzles involve mutable segments. This routine gets a list of the mutable segments. It is substantially faster than individually setting and testing segments. function fsl.FindMutableSegments () print("Finding Mutable Segments -- don't kill during this routine") local slot=fsl.RequestSaveSlot() -- get a save slot for original puzzle quicksave(slot) local mutable={} local isG={} local i select_all() if game.isLigand then deselect_index(game.segCount) end replace_aa('g') -- all mutable segments are set to 'g' for i=1,game.segCount do if get_aa(i) 'g' then -- find the 'g' segments isG+ 1 = i end end replace_aa('q') -- all mutable segments are set to 'q' for j=1,#isG do i=isGj if get_aa(i) 'q' then -- this segment is mutable mutable+ 1 = i end end quickload(slot) fsl.ReleaseSaveSlot(slot) print("Mutables found -- OK to kill if desired") return mutable end A very useful routine. fsl.GetSegCount() -- set game.segCount and game.isLigand game.mutable = fsl.FindMutableSegments() -- get the list of mutable segments for i=1,#game.mutable do print(game.mutablei," is mutable") end fsl.GetSegCount -- get segment count and adjust for ligand puzzle Some functions blow up when applied to ligands. Rather than using get_segment_count throughout one's script one will be well served to call this function then use the variables game.isLigand and game.segCount. function fsl.GetSegCount () game.segCount = get_segment_count() game.isLigand =(get_ss(game.segCount) 'M') -- ligands have ss of 'M' if game.isLigand then game.segCount = game.segCount - 1 end end this function will save you a lot of grief. fsl.GetSegCount() fsl.GetSphere -- get a list of segments within a given distance of a segment Sometimes one wants to get a list of segments within a given distance of a segment. This routine produces such a list. '''function fsl.GetSphere(seg, radius)' sphere={} for i=1, game.segCount do if get_segment_distance(seg,i) <= radius then sphere+ 1=i end end return sphere end Good way to get a list of sements one wants to track or select local sphered=fsl.GetSphere(40,10) fsl.SelectSegments(sphered) -- select only the segments with 10 of 40. do_global_wiggle_all() -- wiggle the selected segments 'fsl.IsHydrophile -- test segment as a hydrophile' Return true if segment is a hydrophile. Hydrophiles are typically on the outside of a protein. function fsl.IsHydrophile (segment) return fsl.aminosget_aa(segment)fsl.aminosHydropathyIndex < 1.6 end Hydrophiles are typically on the outside of a protein for x=1,get_segment_count() do if fsl.IsHydrophile(x) then for y=1,get_segment_count() do local d = get_segment_distance(x,y) if fsl.IsHydrophile(y) and d < 18 then band_add_segment_segment(x,y) band_set_length(get_band_count(),d+2) -- push hydrophiles apart end end end end select_all() do_global_wiggle_all(1) 'fsl.IsHydrophobe -- test segment as a hydrophobe' Return true if segment is a hydrophobe. Hydrophobes are typically on the inside of a protein. There is an undocumented function is_hydrophobic that does the same thing. '''There isn't an equivalent function is_hydrophilic. '''function fsl.IsHydrophobe (segment) return fsl.aminosget_aa(segment)fsl.aminosHydropathyIndex > -1.7 end Hydrophobes are typically on the inside of a protein for x=1,get_segment_count() do if fsl.IsHydrophobe(x) then for y=1,get_segment_count() do local d = get_segment_distance(x,y) if fsl.IsHydropobe(y) and d < 22 then band_add_segment_segment(x,y) band_set_length(get_band_count(),d-2) -- pull hydrophobes together end end end end select_all() do_global_wiggle_all(1) 'fsl.ReleaseSaveSlot -- release a save slot' The variable fsl.SaveSlots and the two functions fsl.ReleaseSaveSlot and fsl.RequestSaveSlot make it easy to add new functions without worrying about which save slots are being used by which routines and keeping them separate. These routines assume one won't release a slot not requested. This is an acceptable risk. -- This code was originally developed by Greg Reddick (aka Tlaloc) fsl.saveSlots={1, 2, 3, 4, 5, 6, 7, 8, 9, 10} -- there are only 10 save slots function fsl.ReleaseSaveSlot(slot) fsl.saveSlots+ 1 = slot end This set of functions makes developing new routines easier local slot=fsl.RequestSaveSlot() -- get a save slot for original puzzle quicksave(slot) -- save original puzzle do_global_wiggle_sidechains(1) local wsScore=get_score(true) local wsSlot=fsl.RequestSaveSlot() -- get a save slot for wiggle sidechains quicksave(wsSlot) -- save puzzle after wiggle sidechains quickload(slot) -- restore original puzzle do_global_wiggle_backbone(1) if wsScore > get_score(true) then print ("sidechains did bettter") quickload(wsSlot) else print ("backbone did better") end fsl.ReleaseSaveSlot(slot) -- release the two requested save slots fsl.ReleaseSaveSlot(wsSlot) 'fsl.RequestSaveSlot -- request a save slot' The variable fsl.SaveSlots and the two functions fsl.ReleaseSaveSlot and fsl.RequestSaveSlot make it easy to add new functions without worrying about which save slots are being used by which routines and keeping them separate. These routines assume all requested save slots will be released. This is an acceptable risk. -- This code was originally developed by Greg Reddick (aka Tlaloc) function fsl.RequestSaveSlot() base.assert(#fsl.saveSlots > 0, 'Out of save slots') local saveSlot = fsl.saveSlots#fsl.saveSlots fsl.saveSlots#fsl.saveSlots = nil return saveSlot end See fsl.ReleaseSaveSlot for an example 'fsl.SelectSegments -- select segments in a list' When one want to restrict an action to a specific set of segments one can use this routine to select them. If you have multiple sets of segments then you can add to a given selection by passing true in the second parameter. -- select a list of segments -- set more to true to add to existing selection function fsl.SelectSegments (list,more) if not more then deselect_all() end for i=1, #list do select_index(listi) end end This limits the do actions to a specific set of segments rather than to all segments for i=1 to game.segCount do if get_segment_scoure(i) < 0 then -- for segments with negative scores fsl.SelectSegments(fsl.GetSphere(i)) set_behavior_clash_importance(.05) -- relax the nearby sidechains do_shake() set_behavior_clash_importance(1) -- wiggle all into place do_select_all() do_global_wiggle_all() end end 'fsl.SelectSphere -- select segments within a given distance of a segment' Sometimes one wants to select segments within a given distance of a segment. function fsl.SelectSphere(seg, radius, more) if not more then deselect_all() end for i=1, game.segCount do if get_segment_distance(seg,i) <= radius then select_index(i) end end end This is a good way to relax sidechains near a segment while keeping the over all structure. fsl.SelectSphere(x,10) -- select segments within 10 units of x fsl.SelectSphere(y,10,true) -- add segments within 10 units of y set_behavior_clash_importance(.05) -- ignore most of clashing do_shake(1) -- shake sidechain, relaxing them select_all() -- select the whole protein set_behavior_clash_importance(1) -- make sure clashing is considered do_global_wiggle_all(1) -- wiggle the whole protein restore_recent_best() -- restore to the best position. 'gary -- Gary Forbis routines you might like' I use another table, game, to contain all my game specific stuff. Rather than doing a lot of work checking for the existance of the table, anyone using these routines should include at a minimum these headers: base={} -- the table containing the lua base routines fsl={} -- the foldit Special Library game={} -- the table containing data about this game games={} -- the table containing data about known games gary={} -- the table containing the gary forbis routines math={} -- the table containing the lua math routines table={} -- the table containing the lua table routines 'gary.BandAdd -- add a band between two segments' Adding a band is pretty simple but rather than recoding the same stuff over and over I use this routine. function gary.BandAdd(seg1,seg2,length,strength) local index if (math.abs(seg1,seg2) < 2) then print("can't add band less than 2 segs apart") return nil else band_add_segment_segment(seg1,seg2) index = get_band_count() if length ~= nil then band_set_length(index,math.max(math.min(length,20),0)) end if strength ~= nil then if strength <= 0 then band_disable(idx) else band_set_strength(index,math.min(strength,10)) end end return index end end If you want the defaults then you don't need this routine bands={} band=gary.BandAdd(1,120,3.5,10) -- create a new band if band ~= nil then bands#bands+1={band,1,120,true,3.5,10} -- save for future reference endif 'gary.BandsAdjust -- adjust the bands in a table' Enable, disable, adjust length or strength of a band. If you want to control your bands then you need to keep information about them in a table. First one fixes the table then calls BandAdjust to make things right. This routine is also useful to reset bands after a quickload provided you don't reload to a point where the bands didn't exist. -- bands is a table of tables with -- index, seg1, seg2, enable, length, strength function gary.BandsAdjust(bands, enable, length, strength) local bandT for x=1,#bands do if enable ~= nil then bandsi4 = enable end if length ~= nil then bandsi5 = length -- tables are passed by reference so this end -- modifies the table being passed if strength ~= nil then bandsi6 = strength end band = bandsx local index = band1 if (band4 false) or (band6 <= 0) then band_disable(index) else band_enable(index) band_set_length(index,math.max(math.min(band5,20),0)) band_set_strength(index,math.min(band6,10)) end end end OK, this isn't a tested routine. I've used something like it but not this one. -- given a table of bands as indicated in gary.BandAdd gary.BandsAdjust(bands,nil,nil,10) -- set all bands to strength 10 gary.BandsAdjust(bands) -- set all bands based upon the table. gary.BandsAdjust(bands,false) -- disable all bands in the table 'gary.BandsDelete -- delete bands at and above an index' I don't think scriipts that run to completion should leave bands lying around except when that's the point. On the other hand I don't think scripts should remove bands it didn't add. There isn't a way to get the state of bands so one can't restore bands the scripts didn't add. I use this to get rid of the bands the script created function gary.BandsDelete(index) if fsl.Default(index,1) < 2 then band_delete() -- this deletes all bands else while index<=get_band_count() do -- band indexes are shifted band_delete(get_band_count()) -- so deleted from the end end end end use when you want to drop bands gary.BandsDelete(5) -- deletes bands at index 5 and above gary.BandsDelete() -- you might as well use band_delete() 'gary.Fuze -- Do blue or pink fuze' I don't know the history of blue and pink fuze. The routine I'm using as the basis for my implementation come from Rav3n's repeat BF 1.1.1. The idea is that by lowering the clashing importance the sidechains relax into a more natural position by doing a shake. Then after setting the clashing importance the stresses are distributed into backbone and sidechain movement. I'm not sure if there is a direct natural counterpart. It seems like there could be because the various chemicals can influence the bonds. 'gary.LoadSS -- Load the games secondary structure' You cannot reload the structure if you didn't save it. Rebuild has greater freedom on loops so many routines convert the segments to loops right up front. function gary.LoadSS(sSeg,eSeg, ssTable) local x=fsl.Default(sSeg,1) local y=fsl.Default(get_segment_count()) x,y=math.min(x,y), math.max(x,y) x,y=math.max(x,1), math.min(y,get_segment_count()) t=fsl.Default(ssTable,game.ss) i=1 while ti3 < x do -- find the triplet with the start segment i = i+1 end repeat if ti1 ~= 'M' then deselect_all() for j=math.max(ti2,x),math.min(ti3,y) do select_index(i) end replace_ss(ti1) end i=i+1 until (i #t) or (ti3 <= y) select_all() -- avoid potential problems down the road end Normal normal secondary structures are E, L, and H. As far as I know M is being used on ligands. Trying to replace the secondary structure on ligands will cause a failure gary.SaveSS() -- save the secondary structure select_all() gary.SetSS('L') -- set the proteins secondary structure to loops gary.LoadSS() -- restore the secondary structure to where it was originally ss={ -- an arbitrary secondary structure list {'L',1,1}, -- loop {'H',2,5}, -- helix {'L',6,10}, {'E',11,20} -- sheet } gary.SetSS(,,ss) -- set secondary based upon ss 'gary.Reband -- Rebuild the bands, dropping disabled bands' Foldit doesn't have band information functions so one has to maintain a table of bands one is using. If you delete a band the index on the bands above the one deleted are moved down. This routine deletes all the bands at or above the minimum band index in the table, deletes the disabled bands from the table, creates new bands, and adjusts them. -- bands is a table of tables with -- index, seg1, seg2, enable, length, strength function gary.Reband (bands) gary.BandsDelete(math.minT(bands,1) band=gary.BandAdd(1,120,3.5,10) for i=#bands,1,-1 do if bandsi4 then local band=bandsi bandsi1=gary.BandAdd(band2,band3,band5,band6) else if i<#bands then bandsi=bands#bands end bands#bands=nil end end end Not much to say --given myBands gary.Reband(myBands) 'gary.SaveSS -- Save the games secondary structure' Rebuild has the greatest freedom on loops. When one want to change to loops then later restore use this routine to save the secondary structure. function gary.SaveSS() game.ss={} local i=1 -- index into game.ss local oldSS=get_ss(1) local sSeg=1 for j=2,get_segment_count() do local newSS=get_ss(j) if oldSS ~= newSS then game.ssi={oldSS, sSeg, j-1} sSeg=j oldSS=newSS i=i+1 end end game.ssi={oldSS, sSeg, get_segment_count()} end this routine uses no parameters and returns none gary.SaveSS() -- save the games secondary structure in game.ss 'gary.SetSS -- Set the games secondary structure' Rebuild has the greatest freedom on loops. I use this routine to convert to loops. I could also use it to set the secondary structure based upon some model of structures and segment ranges. function gary.SetSS(ss,sSeg,eSeg) deselect_all() local x = fsl.Default(sSeg,1) local y = fsl.Default(eSeg,game.segCount) for i=math.max(1,math.min(x,y)), math.min(math.max(x,y),game.segCount) do select_index(i) end replace_ss(ss) select_all() end Common to set the structure to all loops. gary.SetSS{'L') -- set the whole protein to loops. gary.SetSS('L',10,20) -- segments 10 through 20 to loops. 'math -- the lua standard library luaopen_math' Except where identified these routines are in the standard lua library luaopen_math. Please be as faithful as possible to their definitions. 'math.abs -- get the absolute value' Returns the absolute value of x. -- Return the absolute value of x function math.abs(x) if x < 0 then return -x else return x end end﻿ There's not much to say here. You should know when you want this function. print(math.abs(-5)) -- will print 5 print(math.abs(5)) -- will also print 5 ﻿ 'math.ceil -- get the smallest integer at or above a value' Returns the smallest integer larger than or equal to x -- Return the smallest integer at or above x function math.ceil(x) if x x%1 then return x else return x + 1 - (x%1) end end Again, there's not much to say here. print(math.ceil(-1)) -- will print -1 print(math.ceil(-.999)) -- will print 0 print(math.ceil(1.00001)) -- will print 2 'math.floor -- get the largest integer at or below a value' Returns the largest integer smaller than or equal to x. -- Return the smallest integer at or below x function math.floor(x) return x - (x%1) end Again, there's not much to say here. print(math.floor(-1)) -- will print -1 print(math.floor(-.999)) -- will print -1 print(math.floor(1.00001)) -- will print 1 'math.frexp -- get m and e where x = m2e' Returns m and e such that x = m2e, e is an integer and the absolute value of m is in the range [0.5, 1) (or zero when x is zero). function math.frexp(x) local m=x local e=0 local s=1 if m < 0 then s=-1 m = -m end while m>1 do e=e+1 m=m/2 end while m<=.5 do e=e-1 m=m*2 end return m*s, e end I'm not sure how useful this will be. x, y = math.frexp(5) print(5,"=",x,"*(2^",y,")") 'math.ldexp -- return m2e ' Returns m2e (e should be an integer). function math.ldexp(m,e) return m*(2^e) end I'm not sure how useful this will be in foldit. x, y = math.frexp(-20) print(math.ldexp(x,y),"=",x,"*(2^",y,")") 'math.max -- get the largest of a set of numbers' Return the maximum value among its numeric arguments. function math.max(x,...) local max=x local args={...} for i=1,#args do if argsi>max then max=argsi end end return max end Sometimes you want to get the largest from a set of values. max is a good function for this when one isn't dealing with an array of values. print(math.max(1,4,2)) -- will print 4 'math.maxL -- get the largest number in a list' This is a non-standard function useful in foldit. It works like max except that it uses a table of numbers as a parameter. function math.maxL(x) local max=x1 for i=2,#x do if xi>max then max=xi end end return max end You can use this when you want to get the maximum value from a table of values. x=[1, 4, 2} print(math.maxL((x)) -- will print 4 'math.maxT -- get the entry from a table of tables with the largest value at a given position' This is a non-standard function useful in foldit. It works like max except that it returns the table with the largest value in a particular position. The embedded table must have numeric indicies. function math.maxT(x,col) local maxC=x1col local maxI=1 for i=2,#x do if xicol>maxC then maxI=i maxC=xicol end end local maxT={} for i=1,#xmaxI do maxTi=xmaxIi end return maxT end This is a very useful routine when one wants to get the segment index with the best segment score: oldScores={} for x = 1,get_segment_count() do -- this doesn't work on ligand puzzles. oldScores#oldScores+1={x, get_segment_score(x)} end print("max seg score at ",math.maxT(oldScores,2)1) 'math.min -- get the smallest of a set of numbers' Return the minimum value among its numeric arguments. function math.min(x,...) local min=x local args={...} for i=1,#args do if argsi